// Copyright 2007 Google, Inc. All Rights Reserved.

package com.google.jessewilson.codesearch.intellij;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.jessewilson.codesearch.api.CodeFile;
import com.google.jessewilson.codesearch.api.Line;
import com.google.jessewilson.codesearch.api.spi.SearchProviderBackend;
import com.google.jessewilson.codesearch.intellij.dfs.DownloadedFile;
import com.google.jessewilson.codesearch.intellij.dfs.DownloadedFileSystem;
import com.google.jessewilson.codesearch.io.FileDownloader;
import com.google.jessewilson.codesearch.util.Progress;

import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;

import java.awt.*;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.*;

/**
 * @author jessewilson
 */
class OpenFileInIntellij {

  private static final TextAttributes SELECTION_HIGHLIGHTING = new TextAttributes(
      Color.BLACK, Color.YELLOW.brighter(), null, null, 0);

  private final Progress progress;
  private final FileDownloader fileDownloader;
  private final DownloadedFileSystem fileSystem;
  private final Project project;
  private final ExecutorService executorService;

  private OpenFileInIntellij(
      Progress progress,
      SearchProviderBackend searchProviderBackend,
      ExecutorService executorService,
      DownloadedFileSystem downloadedFileSystem,
      Project project) {
    this.progress = progress;
    this.executorService = executorService;

    this.fileDownloader = new FileDownloader(searchProviderBackend);
    this.fileSystem = downloadedFileSystem;
    this.project = project;
  }

  public static class Factory {
    @Inject private Provider<ExecutorService> executorServiceProvider;
    @Inject private Provider<DownloadedFileSystem> downloadedFileSystemProvider;
    public OpenFileInIntellij create(Progress progress, 
        SearchProviderBackend searchProviderBackend,
        Project project) {
      return new OpenFileInIntellij(
          progress,
          searchProviderBackend,
          executorServiceProvider.get(),
          downloadedFileSystemProvider.get(),
          project);
    }
  }

  public void downloadAndShow(final CodeFile codeFile, final Line line) {
    // start a throbber to show progress
    final Progress.Task task = progress.startTask();

    // download off the EDT
    executorService.submit(new Runnable() {
      public void run() {

        final DownloadedFile downloadedFile = fileSystem.getFile(codeFile);
        if(downloadedFile != null) {
          // display on the EDT
          SwingUtilities.invokeLater(new Runnable() {
            public void run() {
              try {
                openLocallyAndShow(downloadedFile, codeFile, line);

              } finally {
                task.finished();
              }
            }
          });

          return;
        }


        final FileDownloader.Result result = fileDownloader.download(codeFile);

        // display on the EDT
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            try {
              showDownloadedResult(result, line);

            } finally {
              task.finished();
            }
          }
        });
      }
    });
  }

  /**
   * Open a local project file.
   */
  public Editor openLocallyAndShow(VirtualFile virtualFile, CodeFile codeFile, Line line) {
    int lineNumber = (line != null)
        ? line.getLineNumber()
        : 0;
    OpenFileDescriptor fileDescriptor = new OpenFileDescriptor(project, virtualFile, lineNumber, 0);
    Editor editor = FileEditorManager.getInstance(project).openTextEditor(fileDescriptor, true);
    if (editor == null) {
      addError(codeFile, new RuntimeException("Failed to create "
         + "editor for localFile \"" + virtualFile.getPath() + "\""));
      return null;
    }

    // TODO(jessewilson): make sure highlighting only happens if the match still matches?
    if (line != null) {
//      selectLine(line, editor);
    }

    return editor;
  }

  private void showDownloadedResult(FileDownloader.Result result, Line line) {
    if (result.isSuccess()) {
      DownloadedFile downloadedFile = fileSystem.addFile(
          result.getCodeFile(), result.getLocalFile());

      Editor editor = openLocallyAndShow(downloadedFile, result.getCodeFile(), line);
      if(editor == null) {
        return;
      }

      // TODO(jessewilson): move this into openLocallyAndShow
      if (line != null) {
        selectLine(line, editor);
      }

    } else {
      addError(result.getCodeFile(), result.getException());
    }
  }

  private void addError(CodeFile codeFile, Exception exception) {
    // TODO(jessewilson): make this onscreen, preferrably in the main UI
    Logger.getLogger(OpenFileInIntellij.class.getName())
        .log(Level.WARNING, "Failed to show " + codeFile, exception);
  }

  private void selectLine(Line line, Editor editor) {
    assert(editor != null);
    assert(line != null);
    // TODO(jessewilson): highlighting should be consistent with Find Usages
    int layer = 1000; // not sure why this has to be so high
    editor.getMarkupModel().addLineHighlighter(line.getLineNumber(), layer,
        SELECTION_HIGHLIGHTING);
  }
}
